#include "stdafx.h"
#include <DirectXColors.h>
#include "common/geoGen.h"
#include "common/app.h"
#include "common/utility.h"
#include "common/material.h"
#include "common/DDSTextureLoader.h"
#include "common/modelLoader.h"
#include "inithelper.h"

void InitHelper::init_adapter()
{
	UINT dxgiFactoryFlags = 0;

#if defined(DEBUG) || defined(_DEBUG)
	{
		ComPtr<ID3D12Debug> debugController;
		ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
		debugController->EnableDebugLayer();

		dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
	}
#endif
	
	ThrowIfFailed(CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory)));
	if (pRender->m_useWarpDevice) // use WARP software
	{
		ComPtr<IDXGIAdapter> warpAdapter;
		ThrowIfFailed(factory->EnumWarpAdapter(IID_PPV_ARGS(&warpAdapter)));
		ThrowIfFailed(D3D12CreateDevice(warpAdapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&pRender->m_device)));
	}
	else // use hardware
	{ 
		// D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&pRender->m_device));
		ComPtr<IDXGIAdapter1> hardwareAdapter;
		auto pfactory = factory.Get();
		for (UINT index = 0; DXGI_ERROR_NOT_FOUND != pfactory->EnumAdapters1(index, &hardwareAdapter); ++index)
		{
			DXGI_ADAPTER_DESC1 desc;
			hardwareAdapter->GetDesc1(&desc);

			if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
				continue;

			if (SUCCEEDED(D3D12CreateDevice(hardwareAdapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&pRender->m_device))))
			{
				std::cout << "using hardware adatper\n";
				std::wcout << std::wstring(L"using hardware adapter : ") + std::wstring(desc.Description) + L"\n";
				return;
			}
		}
		
		ThrowIfFailed(factory->EnumWarpAdapter(IID_PPV_ARGS(&hardwareAdapter)));
		ThrowIfFailed(D3D12CreateDevice(hardwareAdapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&pRender->m_device)));
	}
	std::cout << "using WARP" << std::endl;
}

// cmd queue, list, allocator
void InitHelper::init_commandQLA()
{
	D3D12_COMMAND_QUEUE_DESC queueDesc = {};
	queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
	queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
	ThrowIfFailed(pRender->m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&pRender->m_commandQueue)));

	ThrowIfFailed(pRender->m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
		IID_PPV_ARGS(&pRender->m_commandAlloc)));

	ThrowIfFailed(pRender->m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
		pRender->m_commandAlloc.Get(), nullptr, IID_PPV_ARGS(&pRender->m_commandList)));
}

void InitHelper::init_swapChain()
{
	DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
	swapChainDesc.BufferCount = pRender->FrameCount;
	swapChainDesc.Width = pRender->m_width;
	swapChainDesc.Height = pRender->m_height;
	swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
	swapChainDesc.SampleDesc.Count = 1;

	ComPtr<IDXGISwapChain1> swapChain; // swapchain need command queue to flush
	ThrowIfFailed(factory->CreateSwapChainForHwnd(pRender->m_commandQueue.Get(), App::GetHwnd(),
		&swapChainDesc, nullptr, nullptr, &swapChain));
	ThrowIfFailed(swapChain.As(&pRender->m_swapChain));
	pRender->m_frameIndex = pRender->m_swapChain->GetCurrentBackBufferIndex();
}

void InitHelper::init_dspHeaps() // dsv, rtv are non-shader visible
{
	pRender->m_rtvDescriptorSize = pRender->m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
	pRender->m_dsvDescriptorSize = pRender->m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
	pRender->m_srvDescriptorSize = pRender->m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

	D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
	rtvHeapDesc.NumDescriptors = pRender->FrameCount;
	rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
	rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	ThrowIfFailed(pRender->m_device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&pRender->m_rtvHeap)));

	D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {};
	dsvHeapDesc.NumDescriptors = 1;
	dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
	dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	ThrowIfFailed(pRender->m_device->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&pRender->m_dsvHeap)));
}

void InitHelper::init_rtv()
{
	CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(pRender->m_rtvHeap->GetCPUDescriptorHandleForHeapStart());
	for (UINT i = 0; i < pRender->FrameCount; i++)
	{
		ThrowIfFailed(pRender->m_swapChain->GetBuffer(i, IID_PPV_ARGS(&pRender->m_renderTargets[i])));
		pRender->m_device->CreateRenderTargetView(pRender->m_renderTargets[i].Get(), nullptr, rtvHandle);
		rtvHandle.Offset(1, pRender->m_rtvDescriptorSize);
	}
}

void InitHelper::init_dsv()
{
	D3D12_RESOURCE_DESC dsDesc;
	dsDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
	dsDesc.Alignment = 0;
	dsDesc.Width = pRender->m_width;
	dsDesc.Height = pRender->m_height;
	dsDesc.DepthOrArraySize = 1;
	dsDesc.MipLevels = 1;
	dsDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;
	dsDesc.SampleDesc.Count = 1;
	dsDesc.SampleDesc.Quality = 0;
	dsDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
	dsDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

	D3D12_CLEAR_VALUE clearValue;
	clearValue.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	clearValue.DepthStencil.Depth = 1.0f;
	clearValue.DepthStencil.Stencil = 0;
	ThrowIfFailed(pRender->m_device->CreateCommittedResource(
			&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
			D3D12_HEAP_FLAG_NONE, 
			&dsDesc, 
			D3D12_RESOURCE_STATE_COMMON,
			&clearValue, 
			IID_PPV_ARGS(&pRender->m_depthStencilBuffer)));

	D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
	dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
	dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
	dsvDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	dsvDesc.Texture2D.MipSlice = 0;

	pRender->m_device->CreateDepthStencilView(pRender->m_depthStencilBuffer.Get(), &dsvDesc, 
			pRender->m_dsvHeap->GetCPUDescriptorHandleForHeapStart());
	
	// transition
	pRender->m_commandList->ResourceBarrier(1, 
			&CD3DX12_RESOURCE_BARRIER::Transition(pRender->m_depthStencilBuffer.Get(),
			D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE));
	
}

void InitHelper::init_fence()
{
	ThrowIfFailed(pRender->m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&pRender->m_fence)));
	pRender->m_fenceValue = 1;

	pRender->m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
	if (pRender->m_fenceEvent == nullptr)
		ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
}

void InitHelper::init_geos()
{
	GeometryGenerator geoGen;
	GeometryGenerator::MeshData grid = geoGen.CreateGrid(30.0f, 30.0f, 60, 60);
	GeometryGenerator::MeshData sphere = geoGen.CreateSphere(2.0f, 30, 30);

	
	SubmeshGeometry gridSubmesh;
	gridSubmesh.IndexCount = (UINT)grid.Indices32.size();
	gridSubmesh.StartIndexLocation = 0;
	gridSubmesh.BaseVertexLocation = 0;

	SubmeshGeometry sphereSubmesh;
	sphereSubmesh.IndexCount = (UINT)sphere.Indices32.size();
	sphereSubmesh.StartIndexLocation = grid.Indices32.size();
	sphereSubmesh.BaseVertexLocation = grid.Vertices.size();

	auto totalVertexCount = grid.Vertices.size() + sphere.Vertices.size();

	std::vector<Vertex> vertices(totalVertexCount);

	UINT k = 0;

	for (size_t i = 0; i < grid.Vertices.size(); ++i, ++k)
	{
		vertices[k].Pos = grid.Vertices[i].Position;
		vertices[k].Normal = grid.Vertices[i].Normal;
		vertices[k].Texcoord = grid.Vertices[i].TexC;
	}

	for (size_t i = 0; i < sphere.Vertices.size(); ++i, ++k)
	{
		vertices[k].Pos = sphere.Vertices[i].Position;
		vertices[k].Normal = sphere.Vertices[i].Normal;
		vertices[k].Texcoord = sphere.Vertices[i].TexC;
	}

	std::vector<std::uint16_t> indices;
	indices.insert(indices.end(), std::begin(grid.GetIndices16()), std::end(grid.GetIndices16()));
	indices.insert(indices.end(), std::begin(sphere.GetIndices16()), std::end(sphere.GetIndices16()));

	const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
	const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);

	auto geo = std::make_unique<MeshGeometry>();
	geo->Name = "floorGeo";

	ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
	CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);

	ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
	CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

	geo->VertexBufferGPU = D3DUtil::CreateDefaultBuffer(pRender->m_device.Get(),
		pRender->m_commandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader, 
		D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);

	geo->IndexBufferGPU = D3DUtil::CreateDefaultBuffer(pRender->m_device.Get(),
		pRender->m_commandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader,
		D3D12_RESOURCE_STATE_INDEX_BUFFER);

	geo->VertexByteStride = sizeof(Vertex);
	geo->VertexBufferByteSize = vbByteSize;
	geo->IndexFormat = DXGI_FORMAT_R16_UINT;
	geo->IndexBufferByteSize = ibByteSize;

	geo->DrawArgs["grid"] = gridSubmesh;
	geo->DrawArgs["sphere"] = sphereSubmesh;

	pRender->m_meshes[geo->Name] = std::move(geo);
}

void InitHelper::init_renderItems()
{
	init_materials();
	init_geos();

	auto gridRitem = std::make_unique<RenderItem>();
	XMStoreFloat4x4(&gridRitem->World, XMMatrixScaling(1.0f, 1.0f, 1.0f)*XMMatrixTranslation(0.0f, -4.0f, 0.0f));
	XMStoreFloat4x4(&gridRitem->TexTransform, XMMatrixScaling(4.0f, 4.0f, 4.0f));
	gridRitem->ObjCBIndex = objCBIndex++;
	gridRitem->Mat = pRender->m_materials["tile0"].get();
	gridRitem->Geo = pRender->m_meshes["floorGeo"].get();
	gridRitem->m_primType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
	gridRitem->indexCount = gridRitem->Geo->DrawArgs["grid"].IndexCount;
	gridRitem->startIndexLocation = gridRitem->Geo->DrawArgs["grid"].StartIndexLocation;
	gridRitem->baseVertexLocation = gridRitem->Geo->DrawArgs["grid"].BaseVertexLocation;
	pRender->m_allRitems.push_back(std::move(gridRitem));

/*	
	ModelLoader modelLoader(pRender, XMMatrixRotationX(M_PI_2) * XMMatrixScaling(4.0f, 4.0f, 4.0f));
	modelLoader.loadModel("models/soap/soap.fbx", objCBIndex, matCBIndex, texIndex);
*/
	ModelLoader modelLoader(pRender, XMMatrixRotationX(M_PI_2) * XMMatrixScaling(0.3f, 0.3f, 0.3f) * XMMatrixTranslation(0.0, -4, 0.0));
	modelLoader.loadModel("models/gentiana/gentiana.fbx", objCBIndex, matCBIndex, texIndex);

	// All the render items are opaque.
	for (auto& e : pRender->m_allRitems)
		pRender->m_opaqueRitems.push_back(e.get());

	auto sphereRitem = std::make_unique<RenderItem>();
	XMStoreFloat4x4(&sphereRitem->World, XMMatrixScaling(1.0f, 1.0f, 1.0f) * XMMatrixTranslation(8.0f, -2.0f, 8.0f));
	XMStoreFloat4x4(&sphereRitem->TexTransform, XMMatrixScaling(1.0f, 1.0f, 1.0f));
	sphereRitem->ObjCBIndex = objCBIndex++;
	sphereRitem->Mat = pRender->m_materials["water1"].get();
	sphereRitem->Geo = pRender->m_meshes["floorGeo"].get();
	sphereRitem->m_primType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
	sphereRitem->indexCount = sphereRitem->Geo->DrawArgs["sphere"].IndexCount;
	sphereRitem->startIndexLocation = sphereRitem->Geo->DrawArgs["sphere"].StartIndexLocation;
	sphereRitem->baseVertexLocation = sphereRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;
	pRender->m_allRitems.push_back(std::move(sphereRitem));

	pRender->m_transparentRitems.push_back(pRender->m_allRitems.back().get());

}

void InitHelper::init_materials()
{
	add_material(L"texture/tile.dds", "tile0");
	
	add_material(L"texture/water1.dds", "water1");
	pRender->m_materials["water1"]->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 0.3f);
	pRender->m_materials["water1"]->FresnelR0 = XMFLOAT3(0.1f, 0.1f, 0.1f);
	pRender->m_materials["water1"]->Roughness = 0.0f;
/**/
}

void InitHelper::add_material(const std::wstring& path, const std::string& texName)
{
	auto Tex = std::make_unique<Texture>();
	Tex->Name = texName;
	Tex->Filename = path;
	ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(pRender->m_device.Get(),
		pRender->m_commandList.Get(), Tex->Filename.c_str(),
		Tex->Resource, Tex->UploadHeap));

	pRender->m_textures.push_back(std::move(Tex));

	auto mat = std::make_unique<Material>();
	mat->Name = texName;
	mat->MatCBIndex = matCBIndex++;
	mat->DiffuseSrvHeapIndex = texIndex++;
	mat->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
	mat->FresnelR0 = XMFLOAT3(0.02f, 0.02f, 0.02f);
	mat->Roughness = 0.1f;

	pRender->m_materials[texName] = std::move(mat);
}